#include "glWidget.h"
#include <QKeyEvent>
#include <QDebug>

const GLfloat LIGHT_AMBIENT[] = {0.25, 0.25, 0.25, 1};
const GLfloat LIGHT_DIFFUSE[] = {1, 1, 1, 1};
const GLfloat LIGHT_POSITION[] = {0, 0, 2, 1};

GLWidget::GLWidget(QWidget* parent, unsigned int fps)
  : QGLWidget(parent),
    mFramesPainted(0),
    mXRotation(0),
    mXSpeed(0),
    mYRotation(0),
    mYSpeed(0),
    mZ(-5),
    mLights(false),
    mBlending(false),
    mFilter(0)
{
  connect(&mTimer, SIGNAL(timeout()), this, SLOT(updateGL()));
  mTimer.start(1000/fps);
  mTime.start();
  setFocusPolicy(Qt::StrongFocus);

  qDebug() << "GLWidget started";
}

GLWidget::~GLWidget()
{
  mTimer.stop();
}

void GLWidget::initializeGL()
{
  loadGLTexture("testimage.png", mTextures);
  glEnable(GL_TEXTURE_2D); //enable 2D texture mapping

  glShadeModel(GL_SMOOTH); //smooth shading
  glClearColor(0, 0, 0, 0); //black background

  glColor4f(1, 1, 1, 0.5); //full brightness, 50% alpha
  glBlendFunc(GL_SRC_ALPHA, GL_ONE); //blending function based on alpha

  //depth buffer setup
  glClearDepth(1);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //best perspective correction

  glLightfv(GL_LIGHT1, GL_AMBIENT, LIGHT_AMBIENT);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, LIGHT_DIFFUSE);
  glLightfv(GL_LIGHT1, GL_POSITION, LIGHT_POSITION);
  glEnable(GL_LIGHT1);
}

void GLWidget::resizeGL(int w, int h)
{
  glViewport(0, 0, w, h); //reset viewport

  //setup perspective view
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45, w*1./h, 0.1, 100.);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void GLWidget::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear screen and depth buffer

  glLoadIdentity(); //reset view
  glTranslatef(0, 0, mZ); //move into the screen mZ units

  glRotatef(mXRotation, 1, 0, 0); //rotate about x
  glRotatef(mYRotation, 0, 1, 0); //rotate about y

  glBindTexture(GL_TEXTURE_2D, mTextures[mFilter]); //select first texture
  glBegin(GL_QUADS);
    // Front Face
    glNormal3f(0, 0, 1);
    glTexCoord2f(0, 0); glVertex3f(-1, -1,  1);	// Bottom Left Of The Texture and Quad
    glTexCoord2f(1, 0); glVertex3f( 1, -1,  1);	// Bottom Right Of The Texture and Quad
    glTexCoord2f(1, 1); glVertex3f( 1,  1,  1);	// Top Right Of The Texture and Quad
    glTexCoord2f(0, 1); glVertex3f(-1,  1,  1);	// Top Left Of The Texture and Quad

    // Back Face
    glNormal3f(0, 0, -1);
    glTexCoord2f(1, 0); glVertex3f(-1, -1, -1);	// Bottom Right Of The Texture and Quad
    glTexCoord2f(1, 1); glVertex3f(-1,  1, -1);	// Top Right Of The Texture and Quad
    glTexCoord2f(0, 1); glVertex3f( 1,  1, -1);	// Top Left Of The Texture and Quad
    glTexCoord2f(0, 0); glVertex3f( 1, -1, -1);	// Bottom Left Of The Texture and Quad

    // Top Face
    glNormal3f(0, 1, 0);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -1);	// Top Left Of The Texture and Quad
    glTexCoord2f(0, 0); glVertex3f(-1,  1,  1);	// Bottom Left Of The Texture and Quad
    glTexCoord2f(1, 0); glVertex3f( 1,  1,  1);	// Bottom Right Of The Texture and Quad
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -1);	// Top Right Of The Texture and Quad

    // Bottom Face
    glNormal3f(0, -1,  0);
    glTexCoord2f(1, 1); glVertex3f(-1, -1, -1);	// Top Right Of The Texture and Quad
    glTexCoord2f(0, 1); glVertex3f( 1, -1, -1);	// Top Left Of The Texture and Quad
    glTexCoord2f(0, 0); glVertex3f( 1, -1,  1);	// Bottom Left Of The Texture and Quad
    glTexCoord2f(1, 0); glVertex3f(-1, -1,  1);	// Bottom Right Of The Texture and Quad

    // Right face
    glNormal3f(1, 0, 0);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, -1);	// Bottom Right Of The Texture and Quad
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -1);	// Top Right Of The Texture and Quad
    glTexCoord2f(0, 1); glVertex3f( 1,  1,  1);	// Top Left Of The Texture and Quad
    glTexCoord2f(0, 0); glVertex3f( 1, -1,  1);	// Bottom Left Of The Texture and Quad

    // Left Face
    glNormal3f(-1, 0, 0);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, -1);	// Bottom Left Of The Texture and Quad
    glTexCoord2f(1, 0); glVertex3f(-1, -1,  1);	// Bottom Right Of The Texture and Quad
    glTexCoord2f(1, 1); glVertex3f(-1,  1,  1);	// Top Right Of The Texture and Quad
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -1);	// Top Left Of The Texture and Quad
  glEnd();

  mXRotation += mXSpeed;
  mYRotation += mYSpeed;

  //calculate fps
  ++mFramesPainted;
  if (mTime.elapsed() > 1000)
  {
    emit fps(mFramesPainted * 1000 / mTime.restart());
    mFramesPainted = 0;
  }
}

void GLWidget::keyPressEvent(QKeyEvent *e)
{
  switch (e->key())
  {
  case Qt::Key_L:
    mLights = !mLights;

    if (mLights)
    {
      glEnable(GL_LIGHTING);
      qDebug() << "enable lights";
    }
    else
    {
      glDisable(GL_LIGHTING);
      qDebug() << "disable lights";
    }
    break;
  case Qt::Key_F:
    mFilter = mFilter == 2 ? 0 : mFilter + 1;
    qDebug() << "filter = " << mFilter;
    break;
  case Qt::Key_PageUp:
    mZ -= 0.02;
    qDebug() << "z = " << mZ;
    break;
  case Qt::Key_PageDown:
    mZ += 0.02;
    qDebug() << "z = " << mZ;
    break;
  case Qt::Key_Up:
    mXSpeed -= 0.1;
    qDebug() << "xspeed = " << mXSpeed;
    break;
  case Qt::Key_Down:
    mXSpeed += 0.1;
    qDebug() << "xspeed = " << mXSpeed;
    break;
  case Qt::Key_Right:
    mYSpeed += 0.1;
    qDebug() << "yspeed = " << mYSpeed;
    break;
  case Qt::Key_Left:
    mYSpeed -= 0.1;
    qDebug() << "yspeed = " << mYSpeed;
    break;
  case Qt::Key_B:
    mBlending = !mBlending;
    if (mBlending)
    {
      glEnable(GL_BLEND);
      glDisable(GL_DEPTH_TEST);
    }
    else
    {
      glDisable(GL_BLEND);
      glEnable(GL_DEPTH_TEST);
    }
    break;
  default:
    QGLWidget::keyPressEvent(e);
  }
}

void GLWidget::loadGLTexture(const QString &filename, GLuint textures[3])
{
  //load image
  QImage temp(filename);
  if (temp.isNull())
  {
    temp = QImage(16, 16, QImage::Format_RGB32);
    temp.fill(Qt::green);
  }
  QImage glformatted(QGLWidget::convertToGLFormat(temp)); //convert to gl format

  glGenTextures(3, &textures[0]); //create 3 textures

  glBindTexture(GL_TEXTURE_2D, textures[0]); //create 2d texture at ret
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //filter when image is smaller than texture: worst filter
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //filter when image is larger than texture: worst filter
  glTexImage2D(GL_TEXTURE_2D, //create 2d texture
               0, //level of detail
               3, //number of data components (rgb = 3)
               glformatted.width(), glformatted.height(), //size of texture
               0, //border
               GL_RGBA, //use rgb in this order
               GL_UNSIGNED_BYTE, //image data is unsigned bytes
               glformatted.bits()); //pointer to image data

  glBindTexture(GL_TEXTURE_2D, textures[1]); //create 2d texture at ret
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //filter when image is smaller than texture: good filter
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //filter when image is larger than texture: good filter
  glTexImage2D(GL_TEXTURE_2D, //create 2d texture
               0, //level of detail
               3, //number of data components (rgb = 3)
               glformatted.width(), glformatted.height(), //size of texture
               0, //border
               GL_RGBA, //use rgb in this order
               GL_UNSIGNED_BYTE, //image data is unsigned bytes
               glformatted.bits()); //pointer to image data

  glBindTexture(GL_TEXTURE_2D, textures[2]); //create 2d texture at ret
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //filter when image is smaller than texture: good filter
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST); //filter when image is larger than texture: pick best filter
  gluBuild2DMipmaps(GL_TEXTURE_2D, //create 2d texture
                    3, //number of data components (rgb = 3)
                    glformatted.width(), glformatted.height(), //size of texture
                    GL_RGBA, //use rgb in this order
                    GL_UNSIGNED_BYTE, //image data is unsigned bytes
                    glformatted.bits()); //pointer to image data
}
